
/**
 * 本文件检验FnOnce,FnMut,Fn三个特质
 * FnOnce特质：只能被调用一次.所捕获的变量所有权归还
 * FnMut特质：可以被多次调用，但是捕获的变量必须是可变的,所有权不归还
 * Fn特质：可以被多次调用，所有权归还（不要求捕获的变量必须是可变的）
 */

#[derive(Debug)]
struct Book {
    title: String,
    author: String,
    age: u32,
}

impl Book {
    fn new(title: &str, author: &str, age: u32) -> Self {
        Book { title: title.to_string(), author: author.to_string(), age: age }
    }

    fn print(&self) {
        println!("{} 作者 {}(发行时年龄{})", self.title, self.author, self.age);
    }
}

#[allow(non_snake_case)]
fn test_FnOnce<T: FnOnce()>(f: T) {
    println!("调用FnOnce，只能一次");
    f();
}

#[allow(non_snake_case)]
fn test_FnMut<T: FnMut()>(mut f: T) {
    println!("调用FnMut，多次执行");
    f();
    f();
}
#[allow(non_snake_case)]
fn test_Fn<T: Fn()>(f: T) {
    println!("调用Fn，多次执行");
    f();
    f();
}

fn main() {
    fn_test();
    fn_check();
    //test_move和test_no_move的主要区别是前者内部定义的函数使用了move关键字，后者没有使用。
    //所以，即使二者的匿名函数都是FnOnce，但是由于前者使用了move关键字，所以匿名函数捕获的变量所有权不会归还
    //而后者没有move，所以所有权归还了！
    test_move();
    test_no_move();
}

/**
 * 主要通过特质绑定的方式限定匿名函数的特质，从而限定匿名函数的行为。
 */
fn fn_test() {
    //1.0 测试FnOnce特质
    let book1 = Book::new("唐诗三百首", "孙洙（蘅塘退士）", 54);
    let f1 = || {
        book1.print();
    };
    test_FnOnce(f1);    
    //这个ts.print还可以继续使用，说明它被FnOnce归还了。
    book1.print();

    //2.0 测试FnMut特质
    println!("-----------------------------------------");
    let mut book2 = Book::new("Rust程序设计语言", "Steve Klabnik, Carol Nichols", 45);
    println!("book2地址: {:p}", &book2);
    let mut f2 = move || {
        book2.age += 1;
        book2.print();
        //这里可以明显看出变量地址发生了变化，因为所有权转移了
        println!("book2地址: {:p}", &book2);
    };
    test_FnMut(f2);
    //println!("{}",book2.age);  //book1不可用是因为move转移了所有权，且FnMut需要可变借用

    println!("-----------------------------------------");
    let book3 = Book::new("认识儿童绘画的特定作用", "卢ml", 13);
    println!("book3地址: {:p}", &book3);
    let f3 = || {
        println!("闭包内book3地址: {:p}", &book3);
        book3.print();
    };
    test_Fn(f3);
    println!("{}", book3.age); //book2仍然可用，因为Fn只捕获了不可变引用
    println!("外部book3地址: {:p}", &book3); //验证地址是否相同
}

/**
 * 通过绑定的方式改变匿名函数所实现的特质
 * 检测move关键字的作用：用还是不用其实不重要，主要靠编译器推断
 */
fn fn_check() {
    println!("------------------------------------------------------");
    println!("靠肉眼识别实现了哪一种Fn？");
    println!("------------------------------------------------------");
    let mut name: String = String::from("21世纪的ai战争");
    //这里fx无论是否move都无所谓，因为FnMut必然会自动move
    println!("只要匿名函数内有修改外部变量，必然实现了FnMut,也必然实现了FnOnce特质。");
    let fx = || {
        name.push_str("，人类将如何应对？");
        println!("{}", name);
    };
    let mut boxFx: Box<dyn FnMut()> = Box::new(fx);
    boxFx();
    //println!("{}",name);  // 已经move了，所以这里会报错

    //一个匿名函数是实现了Fn还是FnOnce，纯纯地看如何定义的。
    let name2: String = String::from("认识世界，认识自己从来都不是一件简单当然事情");
    println!("只要匿名函数内没有修改外部变量，必然实现了Fn特质。");
    let fx2 = || {
        println!("{}", name2);
    };
    let boxFx2: Box<dyn Fn()> = Box::new(fx2);
    boxFx2();
    boxFx2();

    println!("虽然fx3和fx2是一摸一样的，但是被boxFx3约束为FnOnce特质，所以不能再调用第二次。");
    let name3: String = String::from("嫁女与征夫，不如弃路旁");
    let fx3 = || {
        println!("{}", name3);
    };
    let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
    boxFx3();
    //boxFx3();  //再调用一次会报错，因为强制使用FnOnce约束
}

/**
 * 这个例子证明move和实现特质的类型没有关系
 */
fn test_move(){
    let name3: String = String::from("嫁女与征夫，不如弃路旁");
    let fx3 = move || {
        println!("{}", name3);
    };
    let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
    boxFx3();
    //println!("{}", name3);  //这里会报错，因为所有权未有归还
}

fn test_no_move(){
    let name3: String = String::from("嫁女与征夫，不如弃路旁");
    let fx3 =  || {
        println!("{}", name3);
    };
    let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
    boxFx3();
    println!("{}", name3);  

    let mut name4=String::from("自嗟贫家女，久致罗褥裳");
    //move还是不move不重要，关键其它地方已经把它定义为FnMut了
    let fx4 =  || {
        name4.push_str("，不若弃路旁");
        println!("{}", name4);
    };
    let mut boxFx4: Box<dyn FnMut()> = Box::new(fx4);
    boxFx4();
    //println!("{}",name4);  //这里会报错，因为所有权已经被归还了。
}